
#include "DTW.hpp"
#include <iostream>
#include <fstream>

#include <string> 
#include <algorithm>
#include <sstream>
#include "./wavio/AudioFile.hpp"

#include "Gist.h"
#include"signalconditioning/Framer.h"
#include"onset-detection-functions/onSets.h"
#include "chroma/FeatureExtractor.h"
using namespace std;
typedef double Real;
struct audioFeature
{
	vector<Real> beats;
	vector<string> beatsId;
	vector< vector<Real> > beatChroma;
	vector<Real> energy;
};

// global variable
  
int sampleRate = 44100;
int frameSize = 1024;
int hopSize = 512;
bool write_audio=true;
int max_f = 5000;
int min_f = 40;
/* audio load function
input : file name(string);
output:  audio signal (vector<Real>)
*/
// returns the sum of the squared array = the energy of the array
template <typename T> T energy(const std::vector<T>& array) {
	return inner_product(array.begin(), array.end(), array.begin(), (T)0.0);
}

// returns the instantaneous power of an array
template <typename T> T instantPower(const std::vector<T>& array) {
	return energy(array) / array.size();
}

// returns the mean of frames
template <typename T>
std::vector<T> meanFrames(const std::vector<std::vector<T> >& frames, int beginIdx = 0, int endIdx = -1) {


	if (endIdx == -1) endIdx = (int)frames.size();
	unsigned int vsize = frames[0].size();

	std::vector<T> result(vsize, (T)0.0);
	typename std::vector<std::vector<T> >::const_iterator it = frames.begin() + beginIdx;
	typename std::vector<std::vector<T> >::const_iterator end = frames.begin() + endIdx;
	for (; it != end; ++it) {
		typename std::vector<T>::const_iterator itFrame = it->begin();
		typename std::vector<T>::const_iterator endFrame = it->end();
		typename std::vector<T>::iterator itResult = result.begin();
		for (; itFrame != endFrame; ++itFrame, ++itResult) {
			*itResult += *itFrame;
		}
	}
	for (unsigned int j = 0; j<vsize; j++) result[j] /= (endIdx - beginIdx);

	return result;
}
int detect_leading_silence(vector<Real>  &audio, Real silence_threshold, int chunk_size)
{
	vector<Real>  windAudio_s;
	unsigned int begin = 0;

	while (begin + chunk_size<audio.size())
	{
		windAudio_s = vector<Real>(audio.begin() + begin, audio.begin() + begin + chunk_size);
		//cout<<instantPower(windAudio_s)<<endl;
		if (instantPower(windAudio_s)>silence_threshold)
		{
			break;
		}
		begin += chunk_size;
	}
	return begin;
}
void audioSilenceRemoval(vector<Real>  &audio)
{
	if (audio.size() <= 2205)
		return;
	int begin = detect_leading_silence(audio, 0.0002, 2205);

	audio = vector<Real>(audio.begin() + begin, audio.end());

	vector<Real> bk = audio;
	reverse(bk.begin(), bk.end());
	int end = detect_leading_silence(bk, 0.0002, 2205);

	vector<Real> windAudio_s = vector<Real>(audio.begin(), audio.end() - end - 1);

	audio = windAudio_s;
}
bool  audioLoader(string fileName, int sampleRate, vector<Real>  &audio)
{
	AudioFile<Real> audioFile;
	bool suc = audioFile.load(fileName);
	if (!suc)
	{
		cout << "file read error" << endl;
		return false;
	}
	int sr = audioFile.getSampleRate();
	if (sr != sampleRate)
	{
		cout << "sampleRate should be 44100" << endl;
		return false;
	}
	audio = audioFile.samples[0];
	/*int begin=detect_leading_silence(audio, 0.0001, 4410);
	vector<Real> bk= audio;
	reverse(bk.begin(),bk.end());
	int end = detect_leading_silence(bk, 0.0001, 4410);
	vector<Real> windAudio_s=vector<Real>(audio.begin()+begin, audio.end()-end-1);
	if(write_audio)
	{
	AudioFile<Real>::AudioBuffer buffer;

	buffer.resize(1);
	buffer[0] = windAudio_s;

	audioFile.setAudioBufferSize(1, windAudio_s.size());
	audioFile.setBitDepth(16);
	audioFile.setSampleRate(44100);
	bool ok = audioFile.setAudioBuffer(buffer);
	audioFile.save("sil.wav");
	}
	audio=windAudio_s;*/
	return true;
}


void beatDetectionByProfile(string &profile, vector<string> &beatsId, vector<Real> &beats, vector< vector<Real> > &chromas, vector<Real> &audio)
{
	ifstream infile(profile.c_str());
	if (infile.is_open())
	{
		string id;
		Real beat_begin;
		Real beat_end;
		char c;
		Real beat_pre = 0;

		string str;
		string mid;
		while (infile >> str)
		{
			stringstream ss(str);
			getline(ss, id, ':');
			beatsId.push_back(id);
			getline(ss, mid, ',');
			getline(ss, mid);
			beat_end = strtof(mid.c_str(), NULL);
			beats.push_back(beat_end / 1000);
		}
	}
	infile.close();

	Framer framer;
	framer.configure(frameSize, hopSize);
	framer.setSource(&audio[0], audio.size());
	unsigned int	m_maxFrames = framer.getMaxNoFrames();
	unsigned int frameIdx = 0;
	vector<Real> audioFrame(frameSize, 0);
	Gist<Real> gist(frameSize, sampleRate);

	
	FeatureExtractor::Parameters params{ float(sampleRate) };
	params.fftSize = frameSize;
	params.useChromaFrequencyMap = true;
	params.minFrequency = min_f;
	params.maxFrequency = max_f;
	FeatureExtractor fe(params);
	while (frameIdx<m_maxFrames)
	{
		framer.getFrame(&audioFrame[0]);
		gist.processAudioFrame(audioFrame);

		vector<double> chroma = fe.process(gist.fftReal,gist.fftImag);
		chromas.push_back(chroma);

		frameIdx++;
	}
}
void beatsDetection(vector<Real> &audio, vector<Real> &beats, vector<string> &beatsId, vector< vector<Real> > &chromas, vector<int> &onsetsIdx)
{
	Framer framer;
	framer.configure(frameSize, hopSize);
	framer.setSource(&audio[0], audio.size());
	unsigned int	m_maxFrames = framer.getMaxNoFrames();
	unsigned int frameIdx = 0;
	vector<Real> audioFrame(frameSize, 0);
	Gist<Real> gist(frameSize, sampleRate);

	FeatureExtractor::Parameters params{ float(sampleRate) };
	params.fftSize = frameSize;
	params.useChromaFrequencyMap = true;
	params.minFrequency = min_f;
	params.maxFrequency = max_f;
	FeatureExtractor fe(params);
	vector<Real> dfs;


	while (frameIdx<m_maxFrames)
	{
		framer.getFrame(&audioFrame[0]);
		gist.processAudioFrame(audioFrame);
		float hfc = gist.highFrequencyContent();
		dfs.push_back(hfc);
		vector<double> chroma = fe.process(gist.fftReal, gist.fftImag);
		chromas.push_back(chroma);

		frameIdx++;
	}
	vector<double> onsets = Onsets(dfs, sampleRate, frameSize, hopSize, 0, onsetsIdx);
	cout << onsets.size() << endl;
	beats = vector<Real>(onsets.begin() + 1, onsets.end());
	beats.push_back(double(audio.size()) / sampleRate);
	for (unsigned int i = 0; i<beats.size(); i++)
	{

		std::stringstream ss;
		std::string str;
		ss << i;
		ss >> str;
		beatsId.push_back(str);
	}

}


/* beat feature extration
input : signal of one beat (vector<Real>)
output: beat feature (vector<Real>)
*/
void beatFeatureExtration(vector< vector<Real> >& chromas, Real begin, Real end, vector<Real>& beatFeature)
{

	begin = ceil(begin*sampleRate / hopSize)>chromas.size() - 1 ? chromas.size() - 1 : ceil(begin*sampleRate / hopSize);
	end = ceil(end*sampleRate / hopSize)>chromas.size() - 1 ? chromas.size() - 1 : ceil(end*sampleRate / hopSize);

	beatFeature = meanFrames(chromas, begin, end);

}


void audioFeatureExtraction(string &audioFilename_u, audioFeature &featureUser)
{

	  /* computes the feature of the user input music */
	  vector<Real>  audio_u;
	  bool issuc=audioLoader(audioFilename_u,sampleRate , audio_u);
	  if(!issuc) exit(1);
	  std::vector<Real> beats_u;
	  vector< vector<Real> > chromas;
	  vector<string> beatsId;
	  vector<int> onsetsIdx;
	  
	  beatsDetection(audio_u, beats_u, beatsId, chromas ,onsetsIdx);
	  
	  featureUser.beats=beats_u;

	  vector<Real>  beatAudio_u;
	  for (unsigned int i=0; i<beats_u.size();i++)
	  {
	  	beatAudio_u.clear();
	  	int begin=0; int end=0;
	  	if(i==0) { begin=0; end=int(beats_u[i]*sampleRate); }
	  	else 
	  	{
	  	  begin=int(beats_u[i-1]*sampleRate);
	  	  end=int(beats_u[i]*sampleRate);
	  	}
	  	copy ( audio_u.begin() + begin, audio_u.begin() + end-1, std::back_inserter(beatAudio_u) );

	  	//audioSilenceRemoval(beatAudio_u);// remove silence

	  	vector<Real> beatFeature;

		beatFeatureExtration(chromas, i == 0 ? 0 : beats_u[i - 1], beats_u[i], beatFeature);

	  	featureUser.beatChroma.push_back(beatFeature);
		featureUser.energy.push_back(instantPower(beatAudio_u));
		std::stringstream sss;
		std::string str1;
		sss<<i;
		sss>>str1;
		
		featureUser.beatsId.push_back(str1);
	  	stringstream ss;
		if(write_audio)
		{
		ss << i;
		string str = ss.str();
	  	string filename=audioFilename_u+"_beat_"+str+".wav";
		AudioFile<Real> audioFile;
		AudioFile<Real>::AudioBuffer buffer;

		buffer.resize(1);
		buffer[0] = beatAudio_u;

		audioFile.setAudioBufferSize(1, beatAudio_u.size());
		audioFile.setBitDepth(16);
		audioFile.setSampleRate(44100);
		bool ok = audioFile.setAudioBuffer(buffer);
		audioFile.save(filename);
	  	}
	  }
}


/*
feature matching of the two inputs
input: user_feature; standard_feature
output: matched features
*/
void featureMatching(vector< vector<Real> >& features_u, vector< vector<Real> >& features_s, 
	vector<int> &matches, vector<Real>& matchScore)
{

     Dtw dtw;
     double distance = dtw.getDistance(features_u, features_s);
     DtwPathType path = dtw.getPath();
	/*
    cout<<"path size: "<< path.size() <<endl;
    for (int i=0; i<path.size(); i++)
    {      
        cout<< "x: "<< path[i].x << " y: "<< path[i].y << " local dis: "<< path[i].dLocal<<endl;
    }*/

     for ( unsigned int s=0; s < features_s.size(); s++)
     {
     	double maxcorr=-1;
     	int max_idx=-1;
     	double corr=0;
     	for(unsigned int  p=0; p<path.size(); p++)
     	{
     	  if (path[p].y == s) 
     	  {
     		corr=1-path[p].dLocal;
     		
     		if(corr>maxcorr)
     		{
     		   max_idx=path[p].x;
     		   maxcorr=corr;
     	    }
     	  }
     	}
     	matches[s]=max_idx;
     	matchScore[s]=maxcorr;
     }
	/*
     cout<<"feature matching completed "<<endl;
     cout<<matches<<endl;*/
}
/* load standard feature from file*/
bool loadStandardModelFromFile(string &standardModel,audioFeature& featureStandard)
{
	ifstream infile(standardModel.c_str());
	if(!infile.is_open())
	{
		cout<<"file not exists"<<endl;
		return false;
	}
	if (infile.is_open()) 
	{
		string id;
		Real beat;
		char c;
		vector<Real> chroma(12,0);
		Real energy;
/* 		while (infile >> id >> c 
			   >> beat>>c>>c
			   >>chroma[0]>>c
			   >>chroma[1]>>c
			   >>chroma[2]>>c
			   >>chroma[3]>>c
			   >>chroma[4]>>c
			   >>chroma[5]>>c
			   >>chroma[6]>>c
			   >>chroma[7]>>c
			   >>chroma[8]>>c
			   >>chroma[9]>>c
			   >>chroma[10]>>c
			   >>chroma[11]>>c>>c
			   >>energy)
		{
				featureStandard.beatsId.push_back(id);
				featureStandard.beats.push_back(beat);
				featureStandard.beatChroma.push_back(chroma);
				featureStandard.energy.push_back(energy);
		} */
		string str;
		string mid;
		while( infile>>str )
		{
			stringstream ss(str);
			getline(ss,id,',');
			featureStandard.beatsId.push_back(id);
			
			getline(ss,mid,',');
			featureStandard.beats.push_back(strtof(mid.c_str(),NULL));

			
			getline(ss,mid,',');
			chroma[0] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[1] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[2] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[3] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[4] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[5] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[6] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[7] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[8] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[9] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[10] = strtof(mid.c_str(),NULL);
			getline(ss,mid,',');
			chroma[11] = strtof(mid.c_str(),NULL);
			featureStandard.beatChroma.push_back(chroma);
						
			getline(ss,mid);
			energy = strtof(mid.c_str(),NULL);
			featureStandard.energy.push_back(energy);
		}	
	}

	infile.close();	
	return true;
}

/* tempo Score and strenthScore function */
void tempoAndStrenthScoreCompute(vector<Real> &beats_s, vector<Real> &beats_u, 
	vector<Real> &energy_s, vector<Real> &energy_u, 
	vector<int> &matches,
	vector<Real> &strenthScore, vector<Real>& tempoScore)
{

	  for (int i=0; i<beats_s.size();i++)
	  {

	  	int begin=0; int end=0;
	  	if(i==0) {  begin=0; end=int(beats_s[i]*sampleRate); }
	  	else 
	  	{
	  	  begin=int(beats_s[i-1]*sampleRate);
	  	  end=int(beats_s[i]*sampleRate) ;
	  	  //end=int(beats_s[i]*sampleRate);
	  	}

	  	double tempo_s=double(end-begin)/sampleRate;
		int pre_match= -1;

		for (int p=i-1; p>=0; p--)
		{
			if(matches[p]!=-1)
			{
				pre_match= matches[p];
				break;
			}
		}
		int match=matches[i];
		if(match==-1) 
		{
			strenthScore.push_back(0);
		     tempoScore.push_back(0);
		}
		else
		{
		  	//int begin=0; int end=0;
		  	double tempo=0;
		  	if(match==0) 
		  	{
		  	 	begin=0; 
		  	 	end=int(beats_u[match]*sampleRate); 
		  	 	tempo=double(end-begin)/sampleRate;
		  	}
		  	else 
		  	{
		  	  begin=int(beats_u[match-1]*sampleRate);
		  	  end=int(beats_u[match]*sampleRate) ;
		  	  if(pre_match==-1)
		  	  	tempo=double(end-begin)/sampleRate;
		  	  else
		  	  {
		  	  	tempo= beats_u[match]-beats_u[pre_match];
		  	  }
		  	}
		  	
		  	double tempoScore_o=tempo>tempo_s? tempo_s/tempo : tempo/tempo_s;

             double corr=0; 
             Real ene_u= energy_u[match];
             Real ene_s= energy_s[i];
             corr=ene_u>ene_s? ene_s/ene_u : ene_u/ene_s;
             strenthScore.push_back(corr);
             tempoScore.push_back(tempoScore_o);
		  }
	  }	
}



bool musicScoreFollowMainWriteFile(string &userAudioFile, string &standardModel, string &outfile)
{
	  //load standard feature from model file
	  audioFeature featureStandard;
	  bool suc=loadStandardModelFromFile(standardModel,featureStandard);

	  if(!suc)
	  {
		  return false;
	  }
	  //extract user feature
	  audioFeature featureUser;
	  audioFeatureExtraction(userAudioFile, featureUser);

	  // dtw matching
	  vector<int> matches(featureStandard.beats.size(),-1);
	  vector<Real> matchScore(featureStandard.beats.size(),-1);
	  featureMatching(featureUser.beatChroma, featureStandard.beatChroma,matches, matchScore);
	  //tempo and strenthScore
	  vector<Real> strenthScore; vector<Real> tempoScore;
	  tempoAndStrenthScoreCompute(
	  featureStandard.beats, featureUser.beats,
	  featureStandard.energy, featureUser.energy,
	  matches,strenthScore,  tempoScore);
	  //score out to file 
	  	  ofstream myfile;
	  myfile.open (outfile.c_str());
	  myfile << "standard_note_index,standard_note_time, match_user_idx, match_user_time,freq_score,  strenthScore, tempoScore"<<endl;
	  for(int i=0; i< matchScore.size(); i++)
	  {
	  	myfile<<featureStandard.beatsId[i]<<","<<featureStandard.beats[i]<<","<< matches[i]<<","  << featureUser.beats[matches[i]] <<","<<matchScore[i]<<","<<strenthScore[i]<<","<<tempoScore[i]<<endl;
	  } 
	  myfile.close();
	  return true;
}


bool musicScoreFollowMainReturnValue(string &userAudioFile, string &standardModel, 
	vector<string>& standardNotesId, vector<Real> &standardNotesTime,
	vector<int>& matchUserNotesId, vector<Real> &matchUserNotesTime,
	vector<Real>& freqScore, vector<Real> &strenthScore,
	vector<Real> &tempoScore)
{
	  //load standard feature from model file
	  audioFeature featureStandard;
	  bool suc=loadStandardModelFromFile(standardModel,featureStandard);
	  if(!suc)
	  {
		  return false;
	  }
	  //extract user feature
	  audioFeature featureUser;
	  audioFeatureExtraction(userAudioFile, featureUser);
	  // dtw matching
	  vector<int> matches(featureStandard.beats.size(),-1);
	  vector<Real> matchScore(featureStandard.beats.size(),-1);
	  featureMatching(featureUser.beatChroma, featureStandard.beatChroma,matches, matchScore);
	  //tempo and strenthScore
	  //vector<Real> strenthScore; vector<Real> tempoScore;
	  tempoAndStrenthScoreCompute(
	  featureStandard.beats, featureUser.beats,
	  featureStandard.energy, featureUser.energy,
	  matches,strenthScore,  tempoScore);

	  for(int i=0; i< matchScore.size(); i++)
	  {
	  	standardNotesId.push_back(featureStandard.beatsId[i]);
	  	standardNotesTime.push_back(featureStandard.beats[i]);
	  	matchUserNotesId.push_back(matches[i]);
	  	matchUserNotesTime.push_back(featureUser.beats[matches[i]]);
	  	freqScore.push_back(matchScore[i]);
	  } 
	  return true;
}